// TODO fix logic for displaying Put
// TODO accept paths with line numbers
// TODO open multiple files

package main

import (
	"flag"
	"fmt"
	"image"
	"os"
	"runtime"
	"runtime/pprof"
	"time"

	"code.google.com/p/jnj.plan9/draw"
	"code.google.com/p/jnj/cmd/util"
	"code.google.com/p/jnj/graphics/plan9"
	"code.google.com/p/jnj/graphics/text"
)

var (
	disp      *draw.Display
	screen    *draw.Image
	pix       draw.Pix
	screenbuf *draw.Image
	bordercol *draw.Image
	clipchan  chan string
	fontpath  string

	mainwin     *window
	resizetimer *time.Timer
	currentfile string

	cpuprofile = flag.String("cprof", "", "write cpu profile to file")

	die = util.Die
)

func main() {
	runtime.GOMAXPROCS(4)
	flag.Parse()
	inputfile := false
	if len(flag.Args()) == 1 {
		inputfile = true
	} else if len(flag.Args()) > 1 {
		fmt.Fprintln(os.Stderr, "Usage: buf [file]")
		os.Exit(1)
	}

	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		die(err)
		pprof.StartCPUProfile(f)
	}

	var err error
	disp, err = draw.Init(nil, "buf", "700x600")
	die(err, "error initializing display")
	mouse := disp.InitMouse()
	clipchan = plan9.SetupClipChan(disp)

	fontpath = os.Getenv("ttf")
	if fontpath == "" {
		gopath := os.Getenv("GOPATH")
		fontpath = gopath + "/src/code.google.com/p/jnj/cmd/buf/proggy.ttf"
	}
	screen = disp.ScreenImage
	r := image.Rect(0, 0, screen.Bounds().Dx(), screen.Bounds().Dy())
	mainwin = newWindow(r)

	// disp.ScreenImage Pix may not match image.RGBA, create a buffer that does
	pix, err = draw.ParsePix("x8b8g8r8")
	die(err)

	resize() // sets up screenbuf

	go domouse(mouse)

	if inputfile {
		currentfile = flag.Arg(0)
		f, err := os.OpenFile(currentfile, os.O_CREATE, 0644)
		if err == os.ErrPermission {
			// try to open it read-only
			f, err = os.Open(currentfile)
			die(err)
		}
		mainwin.edit.Load(f)
		f.Close()
		if currentfile[0] != '/' {
			cwd, err := os.Getwd()
			die(err, "error getting current working directory")
			currentfile = cwd + "/" + currentfile
		}
	}
	mainwin.edit.PushState()
	updatetag()
	p := image.Pt(len(mainwin.tag.Contents()[0]), 0)
	mainwin.tag.Select(text.Address{Left: p, Right: p})
	drawborder(mainwin.tag)

	for {
		select {
		case editimg := <-mainwin.edit.DrawChan:
			r := editimg.Bounds()
			if r != mainwin.edit.Bounds() || !r.In(screenbuf.Bounds()) {
				continue // a resize has invalidated this image
			}

			_, err := screenbuf.Load(r, editimg.Pix)
			die(err)
			screen.Draw(screen.Bounds(), screenbuf, nil, image.ZP)
			disp.Flush()

		case tagimg := <-mainwin.tag.DrawChan:
			r := tagimg.Bounds()
			if r != mainwin.tag.Bounds() || !r.In(screenbuf.Bounds()) {
				continue // a resize has invalidated this image
			}

			_, err := screenbuf.Load(tagimg.Bounds(), tagimg.Pix)
			die(err)
			screen.Draw(screen.Bounds(), screenbuf, nil, image.ZP)
			disp.Flush()

		case <-mouse.Resize:
			resize()
			disp.Flush()
		}
	}
}

// draw a horizontal line at buf.Bounds().Max.Y+1
func drawborder(buf *text.Buffer) {
	if bordercol == nil {
		var err error
		bordercol, err = disp.AllocImage(disp.Image.R, screenbuf.Pix, true, draw.Purpleblue)
		die(err, "error allocating bordercol")
	}

	borderr := screenbuf.Bounds()
	borderr.Min.Y += buf.Bounds().Max.Y
	borderr.Max.Y = buf.Bounds().Max.Y + 1
	screenbuf.Draw(borderr, bordercol, nil, image.ZP)
}

func resize() {
	err := disp.Attach(draw.Refmesg)
	die(err, "error reattaching display after resize")

	r := image.Rect(0, 0, screen.Bounds().Dx(), screen.Bounds().Dy())
	screenbuf, err = disp.AllocImage(r, pix, false, draw.White)
	die(err)

	if resizetimer == nil {
		resizetimer = &time.Timer{}
	} else {
		resizetimer.Stop()
	}
	resizetimer = time.AfterFunc(100*time.Millisecond, func() {
		mainwin.resize(r)
	})
}
